Профилирование программ

        Профилированием программы называют процесс, в результате которого по конкретным наборам входных данных получают информацию о требуемых ресурсах памяти и времени для выполнения ее отдельных фрагментов, а также информацию о ее частотных характеристиках таких, как количество обращений к процедурам и количество вызовов из процедур. Эти характеристики дают возможность наглядно увидеть узкие места программы и при необходимости уточнить или изменить используемый алгоритм вычислений. Профилирование программ способствует оптимизации их кода и обычно проводится с помощью специально созданных для этих целей утилит.

        Cредства профилирования файлов в Maple подразделяются на два типа: те, которые расположены в ядре системы и те, которые сосредоточены в пакете CodeTools и его подпакетах. Мы будем вести речь только о средствах первого типа, к которым относятся команды profile, showprofile, unprofile, exprofile, excallgraph и т. д. 

Профилирование файлов (1)    

        Команда profile(pro1, pro2, …) подготавливает процедуры pro1, pro2, … к профилированиию, если этого не сделано ранее. Указанная подготовка состоит в физическом изменении внутреннего представления процедур таким образом, чтобы можно было собирать различную информацию о них во время выполнения. Если по подготовленной к профилированию процедуре проведены вычисления, то будем называть ее профилированной. Все данные, собранные по профилированным процедурам, хранятся в памяти и, при необходимости, могут быть выведены командой showprofile([pro1, pro2, …], [so]). Успешно выполненная команда profile(pro1, pro2, …) возвращает NULL.

        Команда showprofile([pro1, pro2, …] в табличной форме выводит данные по профилированным процедурам pro1, pro2, … Если в аргументах список процедур не указан, то выводятся данные обо всех профилированных процедурах. Структуру таблицы вывода можно видеть в примерах 1-3, где заголовки столбцов имеют следующий смысл:

·       function – имя процедуры;

·       depth (глубина) – глубина рекурсии;

·       calls (вызовы) – количество вызовов процедуры;

·       time – время вычисления процедуры;

·       time% – время вычисления процедуры в процентах по отношению к суммарному времени вычисления всех профилированных процедур;

·       bytes – размер памяти в байтах, занимаемой процедурой;

·       bytes% – размер памяти в процентах, занимаемой процедурой, по отношению к суммарному объему памяти, отведенному всем профилированным процедурам.

        В строке total (итоги) – приведены сводные данные по столбцам таблицы вывода для всех профилированных процедур.

        Необязательный параметр so указывает тип сортировки данных таблицы вывода. Возможные значения so таковы:

·       alpha – сортировка по именам функций в лексикографическом порядке (столбец function);

·       ralpha – сортировка по именам функций в порядке, обратном к лексикографическому (столбец function);

·       time – сортировка по неубыванию времени работы центрального процессора при выполнении процедур (столбец time);

·       rtime – сортировка по невозрастанию времени работы центрального процессора при выполнении процедур (столбец time);

·       bytes – сортировка по неубыванию размера памяти в байтах, занимаемых процедурами (столбец bytes);

·       rbytes – сортировка по невозрастанию размера памяти в байтах, занимаемых процедурами (столбец bytes);

·       load – сортировка по неубыванию по виртуальному столбцу со значениями bytes2×time;

·       rload – сортировка по невозрастанию по виртуальному столбцу со значениями bytes2×time.

        Команда unprofile(pro1, pro2, …) удаляет из памяти всю информацию по профилированным процедурам pro1, pro2, …, восстанавливает внутреннее представление этих процедур до их оригинального состояния и возвращает NULL. Если в аргументах C список pro1, pro2, … не указан, то речь идет обо всех профилированных процедурах.

        Команда resetprofile() равносильна выполнению unprofile().

        Вычисления по профилированным процедурам могут быть неэкономными, как по времени, так и по размеру требуемой памяти. Поэтому их следует использовать лишь для профилирования, но не для реальных вычислений.

        Пример 1. Вспомним постановку занимательной задачи о счастливых билетах. Пусть номера автобусных проездных билетов представляют собой натуральные шестизначные числа. Будем предполагать, что они принадлежат диапазону 000001 – 999999. Назовем билет счастливым, если сумма его первых трех цифр равна сумме трех последних цифр. Составить программу определения количества счастливых билетов. Провести ее профилирование.

        Решение. Решать эту задачу прямым перебором всех возможных вариантов возможно, но неэффективно. Не мешает поискать какой-либо иной прием. Воспользуемся тем, что всего имеется 27 возможных значений суммы трех цифр для чисел указанного диапазона (не считая нулевой суммы для второй тройки). Можно подсчитать, сколькими способами достигается каждое значение этой суммы. Далее, следует учесть, что для конкретной суммы любые способы ее получения в первой и второй тройках могут комбинироваться друг с другом. Общее количество этих комбинаций для всех возможных сумм и будет давать количество счастливых билетов.

        На описанных идеях и построена процедура happy, решающая поставленную задачу для шестизначных чисел (k = 6) и более общую задачу для билетов с четным значением k ³ 2. Обратите внимание, что формальный параметр k в happy должен быть целым положительным (posint) и одновременно четным (even). Поэтому его тип является структурным, а объявлен он в виде k::And(posint, even).

        По happy решены три контрольных примера – happy(4), happy(6) и happy(10). После этого для happy(8) реализовано профилирование.

 

> 

restart:

{¿}

 

happy := proc(k::And(posint, even))

{Shift+¿}

 

   local q, t, p, j, su, n;

{Shift+¿}

 

   q := k/2: t := 9*q:

{Shift+¿}

 

   p := [seq(0, i = 1..t)]:

{Shift+¿}

 

   for j from 1 to 10^q-1 do

{Shift+¿}

 

      su := 0: n := j:

{Shift+¿}

 

      while n <> 0 do

{Shift+¿}

 

         su := su+modp(n, 10): n := floor(n/10):

{Shift+¿}

 

      end do:

{Shift+¿}

 

      p[su] := p[su]+1:

{Shift+¿}

 

   end do:

{Shift+¿}

 

   j := 'j': sum(p[j]^2, j = 1..t)

{Shift+¿}

 

end proc:

{¿}

> 

happy(4), happy(6), happy(10);

{¿}

> 

profile(happy);

{¿}

> 

happy(8):

{Shift+¿}

 

showprofile(happy);

{¿}

 

> 

unprofile()

        Пример 2. Нас могут не устраивать системные сообщения об ошибках, появляющиеся при обращениях к процедуре happy предыдущего примера с недопустимыми значениями аргумента k (5, 7.1, -8, …). Написать аналог happy, проверяя тип k в отдельной процедуре correctly, и формируя в ней подходящие сообщения об ошибках при неправильных обращениях. Для созданных процедур happy и correctly провести профилирование.

        Решение. В happy необходимо сделать следующие изменения: в заголовке убрать проверку типа ::And(posint, even) и вслед за строкой local объявления локальных переменных поместить обращение correctly(k). Процедура correctly может быть, например, такой, как это указано ниже.

 

> 

restart:

{¿}

 

happy := proc(k)

{Shift+¿}

 

   local q, t, p, j, su, n;

{Shift+¿}

 

   correctly(k):

{Shift+¿}

 

   … # см. предыдущий вариант happy

 

end proc

{¿}

> 

correctly := proc(k)

{Shift+¿}

 

  if not(type(k, posint))

{Shift+¿}

 

     then error("%1 is not posint", k)

{Shift+¿}

 

  elif not(type(k, even))

{Shift+¿}

 

     then error("%1 is not even", k)

{Shift+¿}

 

  end if

{Shift+¿}

 

end proc:

{¿}

 

 

 

> 

profile(happy);

{Shift+¿}

 

happy(10):

{Shift+¿}

 

showprofile(happy);

{¿}

 

> 

unprofile()

        Пример 3. Составить рекурсивную программу вычисления приближения к основанию натуральных логарифмов, то есть к числу е, используя следующий алгоритм Ламберта:

 

 

 

        Провести профилирование созданной программы.

        Решение. Указанный алгоритм предложен Ламбертом в 1766 году. Организовать по нему рекурсивные вычисления труда не составляет. Здесь величины a(k) и b(k) задаются рекуррентными соотношениями, похожими на определение чисел Фибоначчи, но нелинейными. Однако это обстоятельство не привносит каких-либо дополнительных затруднений в программную реализацию соответствующих функций. Дело в том, что задание последовательности любыми рекуррентными соотношениями сразу решает проблему рекурсивной триады: осуществлена параметризация задачи, выделена база рекурсии и задана декомпозиция.

        Процедура e(k) приближенно вычисляет e, обращаясь к рекурсивным процедурам a(k) и b(k). Уже в e(10) первые 25 цифр совпадают с соответствующими цифрами e. Профилирование реализовано для e(20).

> 

restart:

{¿}

> 

a := proc(k)

{Shift+¿}

 

   if k < 2 then k+1

{Shift+¿}

 

      else (4*k-2)*a(k-1)+a(k-2)

{Shift+¿}

 

   end if

{Shift+¿}

 

end proc:

{¿}

 

 

 

 

b := proc(k)

{Shift+¿}

 

   if k < 2 then k

{Shift+¿}

 

      else (4*k-2)*b(k-1)+b(k-2)

{Shift+¿}

 

   end if

{Shift+¿}

 

end proc:

{¿}

 

 

 

 

e := proc(k::nonnegint)

{Shift+¿}

 

   local x, y;

{Shift+¿}

 

   x := a(k): y := b(k): (x+y)/(x-y)

{Shift+¿}

 

end proc:

{¿}

 

 

 

> 

[e(5), e(7), e(10)];

{Shift+¿}

 

evalf[25](%[3]-exp(1));

{¿}

> 

profile(e, a, b);

{Shift+¿}

 

e(20):

{Shift+¿}

 

showprofile(e, a, b);

{¿}

 

> 

unprofile()

        Учитывая, что последовательности a(k) и b(k) (k = 0, 1, 2, …) определяются весьма схожим образом, попробуем построить одну общую процедуру c(k, t) для вычисления их членов. Кроме того, в c(k, t) опцией remember включим механизм запоминания таблицы вычисляемых значений. Это существенно уменьшит время вычислений. Новый вариант программы мог бы быть таким:

 

> 

c := proc(k, t)

{Shift+¿}

 

   option remember;

 

 

   if k < 2 then k+t

{Shift+¿}

 

      else (4*k-2)*c(k-1, t)+c(k-2, t)

{Shift+¿}

 

   end if

{Shift+¿}

 

end proc:

{¿}

> 

e := proc(k::nonnegint)

{Shift+¿}

 

   local x, y;

{Shift+¿}

 

   x := c(k, 1): y := c(k, 0): (x+y)/(x-y)

{Shift+¿}

 

end proc:

{¿}

> 

profile(e, a, b);

{Shift+¿}

 

e(20):

{Shift+¿}

 

showprofile(e, a, b);

{¿}

> 

unprofile()

Профилирование файлов (2)

        Команда writeto(file) переключает вывод результатов вычислений с терминала на вывод в новый или существующий текстовый файл с именем file. После ввода команды по выполняемым в документе командам вывод направляется не на экран дисплея, а в файл file. По умолчанию в file записываются: символ приглашения, команда, результаты вычислений и некоторая служебная ресурсная информация. Eсли файл с именем file уже существует, то его прежнее содержимое не сохраняется. 

        Команда appendto(file) выполняется аналогично, но здесь запись ведется в существующий файл file, причем все его данные сохраняется, а новые данные дополняют их.

        Команда writeto(terminal) восстанавливает после writeto(file) стандартный режим вывода результатов вычислений на терминал.

        Команда exprofile(file, so) выводит из файла file информацию по профилированию программы. Указанный файл должен быть заранее создан при установке kernelopts(profile = a) с a = true. По exprofile формируется и выводится строка итогов и таблица данных. В каждой строке таблицы размещается следующая информация: имя процедуры или головной программы (name), количество вызовов (#calls), время выполнения в секундах (cpu) и занимаемая память в словах (words). Возможные значения необязательного парамера so и устанавливаемые ими сортировки те же, что и у функции profile за следующим исключением. Значения byte и rbyte заменены соответственно на word и rword.

        Команда . excallgraph(file, so) дополняет команду exprofile(file, so). По ней в строках выводимой таблицы размещается имя вызывающей процедуры, головной программы или запуска (Caller) и  имя вызываемой процедуры (Callee). Кроме того, для последней процедуры выводится количество вызовов (#calls), время выполнения в секундах (time) и размеры занимаемой памяти в словах (words).

        Команда kernelopts(profile = a) устанавливает (a = true) или отменяет (a = false) режим генерации ресурсной информации при вызовах процедур и выходах из них.

        Пример 4. Здесь приведен пример профилирования экзотической программы из трех процедур командами exprofile и excallgraph. Процедура a – головная, процедура с – рекурсивная, а процедуры b и c – косвенно рекурсивны (b обращается к с, а с обращается к b).

 

> 

restart:

{¿}

> 

a := proc(n::posint) c(n) end proc:

{¿}

> 

b := proc(n) c(n+1) end proc:

{¿}

> 

c := proc(n)

{Shift+¿}

 

   if n = 1 then 1

{Shift+¿}

 

      elif modp(n, 2) = 0 then c(n/2)

{Shift+¿}

 

      else print(n); b(3*n)

{Shift+¿}

 

   end if

{Shift+¿}

 

end proc:

{¿}

 

 

 

> 

fil := "e:\\d_1\\are\\current\\proba":

{Shift+¿}

 

kernelopts(profile = true):

{Shift+¿}

 

writeto(fil);

{Shift+¿}

 

a(1111111199);

{Shift+¿}

 

kernelopts(profile = false);

{Shift+¿}

 

writeto(terminal);

{Shift+¿}

 

exprofile(fil, alpha);

{Shift+¿}

 

excallgraph(fil, alpha);

{¿}

          

        Во многих случаях помощь в профилировании файлов могут оказать функции showtime и time. По последней из них можно вычислять время выполнения отдельных фрагментов процедур.

Режима показа времени вычислений

        Функция showtime() инициирует возможность ввода-вывода данных в текущей сессии в специальном режиме showtime (показ времени вычислений). После запуска этот режим включается по умолчанию, что демонстрируется сменой стандартного знака приглашения “>” на составной знак “O1:=”.  Выполнение группы предложений, введенных после знака “O1:=”, приводит к выводу результатов вычислений и сообщения, в котором указывается время вычисления данной группы в секундах (time = …) и используемая для этих вычислений память в байтах (bytes = …). Кроме того, появляется приглашение “O2:=” для ввода следующей группы предложений и т. д.

        Команда off переключает ввод с режима показа времени вычислений на стандартный режим с приглашением в виде знака “>”. Предполагается, что режим показа времени уже инициирован, то есть функция A выполнена.

        Команда on переключает ввод со стандартного режима на режим показа времени вычислений. Предполагается, что режим показа времени уже инициирован, то есть функция A выполнена.

        Примеры 5. Здесь демонстрируется работа в документе в стандартном режиме и в специальном режиме показа времени вычислений. 

> 

restart:

{¿}

> 

top := proc(k::posint)

{Shift+¿}

 

   local j;

{Shift+¿}

 

   for j  from 1 to k do k^3 end do

{Shift+¿}

 

end proc:

{¿}

> 

showtime():

{¿}

O1:=

top(1000);

{¿}

O2:=

x^2+y^3+z^4;

{¿}

O3:=

off;

{¿}

> 

x^2+y^3;

{¿}

> 

on;

{¿}

O1:=

a+b+1+top(10000):

{¿}

O2:=

off;

{¿}

> 

        Дополнительные возможности профилирования файлов обеспечиваются командами пакета CodeTools и командами его подпакетов. Кроме того, в пакете сodegen имеется команда cost, по которой подсчитывает количество операций, которые необходимо выполнить при числовых вычислениях выражений expr или процедур. Эта команда будет описана в разделе “Преобразование процедур”.

Дополнительные средства создания процедур

        Ранее мы определяли пользовательские процедуры или с помощью ключевого слова proc в виде “proc(…) … end proc”, или функциональным оператором в форме “(…) -> expr”. Эти способы определения процедур назовем стандартными. Однако имеются и иные средства конструирования процедур. К ним относятся функции создания и модификации процедур наборами правил (define, definemore, undefine, redefine), функции формирования процедур по выражениям и переменным (unapply, codegen[makeproc]) и функция конвертации в процедуры последовательностей выражений (CompSeq). Получаемые указанными средствами процедуры в свойствах могут иметь отличия от соответствующих им процедур, созданных стандартными способами. Кроме того, по define, definemore, undefine и redefine можно определять пользовательские операторы.

Определение процедур набором правил

        Команда define(name, ru1, ru2, …) определяет процедуру name с помощью правил, заданных аргументами ru1, ru2, … и возвращает значение NULL. Правила ru1, ru2, … представляются отдельными ключевыми словами или уравнениями вида name… = expr. Иными словами, левая сторона уравнений должна начинаться с имени создаваемой процедуры, например, gcd(a::integer, 0) = a, zuuz(0) = hhh, gcd(a::integer, b::integer ) = gcd(b, a mod b)), а в правой стороне могут находиться произвольные выражения. При вызовах процедуры name в вычислениях применяется порядок правил, заданный аргументами команды. Поэтому, более важные и специфические правила должны располагаться в последовательности ru1, ru2, … ближе к ее началу. Наиболее простой и типичный синтаксис – define(name, name(x::ty1, y::ty2, …) = expr), где x, y, … – формальные аргументы, ty1, ty2, … – типы аргументов и expr – выражение. Очень удобно определять с помощью define рекурсивные процедуры, записывая для них в виде правил и базу рекурсии, и декомпозицию.

        Отдельные ключевые слова в правилах могут быть такими:

·       linear  слово, устанавливающее свойство линейности процедуры name по ее первому аргументу;

·       multilinear – слово, устанавливающее свойство линейности процедуры name по всем ее аргументам;

·       orderless – слово, устанавливающее свойство независимости значений процедуры name от любой перестановки ее аргументов. Для процедуры с двумя аргументами (бинарного оператора) это свойство коммутативности;

·       flat – слово, устанавливающее свойство ассоциативности процедуры (оператора) name;

·       identity = expri – уравнение, задающее словом identity единичный элемент в виде выражения expri. Процедура name должна иметь два аргумента (оператор nameбинарный);

·       zero = expro – уравнение, задающее словом zero нулевой элемент в виде выражения expro;

·       diff(name(x), x) = expr – уравнение, задающее словом diff производную от name по x в виде выражения expr. Процедура name должна иметь один аргумент.

Различия в свойствах элементов и в выполнении процедуры

при разных определениях

Oпределение по define

Стандартное определение

1.                  

Имя процедуры name не должно иметь значения, то есть требуется выполнение условия assigned(name) = false

Никаких условий на имя процедуры не накладывается

2.                  

Для всех формальных параметров должен быть указан тип

Тип формальных параметров указывать не обязательно

3.                  

При обращении к процедуре с фактическими аргументами неправильного типа возвращается невычисленное значение

При обращении к процедуре с фактическими аргументами неправильного типа возвращается сообщение об ошибке

4.                  

При обращении к процедуре с количеством аргументов большим, чем требуется, возвращается невычисленное значение

При обращении к процедуре с количеством аргументов большим, чем требуется, лишние аргументы игнорируются

5.                  

Имеются ограничения на использование средств Maple в теле процедуры 

Ограничений на использование средств Maple нет

        Команда definemore(name, ru1, ru2, …) имеет синтаксис, аналогичный define, и используется для задания дополнительных свойств процедуре name, созданной по define. Выполнение ее добавляет к имеющимся свойствам name новые свойства, определяемые правилами из definemore.

        Команда undefine(name) удаляет из памяти определение процедуры name, созданной по команде define.

        Команда redefine(name, [ru1, ru2, …]) имеет синтаксис, аналогичный define, и используется для переопределения процедуры name, созданной по define. Выполнение отменяет свойства name, определенные в define, и назначает новые свойства, определяемые правилами из redefine.

        Пример 5. Написать с помощью команды define рекурсивную функцию вычисления факториала целого неотрицательного числа n.

        Решение. Пусть fa(n) – искомая функция. Тогда базу рекурсии  fa(0) = 1” и декомпозицию “fa(n) = n*fa(n–1)” можно задать в define в виде правил. Учитывая, что при выполнении fa правила из define будут использоваться в порядке их записи, сначала необходимо указать базу рекурсии. Это приводит к следующему определению: 

> 

define(fa, fa(0) = 1, fa(n::posint) = n*fa(n-1));

{¿}

> 

[fa(0), fa(1), fa(5), fa(10)];

{¿}

> 

undefined(fa);

{¿}      

        Пример 6. Написать с помощью команды define рекурсивную функцию нахождения n-го числа последовательности Фибоначчи: fib(1) = fib(2) = 1; fib(n) = fib(n–1)+fib(n–2)  (n > 2, n – натуральное число).

        Решение. Как и в предыдущем примере, базу рекурсии и декомпозицию зададим в define в виде правил. Это приводит к следующему определению: 

 

> 

define(fib, fib(0) = 1, fib(1) = 1,

{Shift+¿}

 

   fib(n::posint) = fib(n-1)+fib(n-2));

{¿}

> 

[fib(0), fib(1), fib(5), fib(10)];

{¿}

> 

undefined(fib)

{¿}      

        Примеры 7. Демонстрация использования правила linear при определении процедуры.

> 

define(fli, linear, fli(1) = a);

{¿}

> 

fli(2*x + 3*y);

{¿}

> 

fli(2*x + 3);

{¿}

> 

simplify(exp(y+ln(exp(fli(x*ln(2))))));

{¿}

> 

undefine(fli);

{¿}      

        Примеры 8.  “Неправильные” вычисления по define.

> 

define(pr, pr(a::string) = length(a)):

{¿}

> 

pr("qwe");

{¿}

> 

pr := 'pr': define(pr, pr(a) = length(a)):

{¿}

> 

pr("qwe");

{¿}

> 

pr := proc(a::string) length(a) end proc:

{¿}

> 

pr("qwe");

{¿}

> 

define(pro, pro(a::symbol, n::posint) = cat(a, 1..n)):

{¿}

> 

pro(b, 7);

{¿}

> 

pro := proc(a::symbol, n::posint)

{Shift+¿}

 

   cat(a, 1..n)

{Shift+¿}

 

end proc;

{¿}

> 

pro(b, 7);

{¿}

 

Определение процедур по выражениям и переменным (1)

        Команда g := unapply(expr,  x::ty1, y::ty2, …, opts)  возвращает именованную процедуру g := (x, y, …) ® expr (функциональный оператор). Дальнейшие обращения к g осуществляются обычным образом в форме g(a, b, …), где a, b, … – последовательность фактических значений соответствующих формальным параметрам x, y, … При необходимости получить неименованную процедуру (x, y, …) ® expr, в unapply следует опустить левую часть вместе со знаком присваивания. Тогда обращения к ней должны выполняться или в виде unapply(expr,  x, y, …)(a, b, …), или с помощью дитто-операторов, например %(a, b, …). Чтобы использовать возвращаемую процедуру с неопределенным числом аргументов, переменные x, y, … в unapply указывать не нужно.

        Опции opts могут быть такими:

·         proc_options = {a1, a2, … }, где ak Î {arrow, inline, operator, remember, system}. Действие ключевых слов arrow, inline, operator, remember и system то же самое, что и соответствующих опций в стандартном определении процедуры;

·         numeric = [z1, z2, …], где элементы в списке являются именами каких-либо ведущих переменных. Если переменная одна, то ее можно указать и без списка. Наличие этой опции приводит к генерации такой процедуры, у которой в теле проверяется, имеют ли переменные z1, z2, … тип numeric. Если это не так, то значение возвращается невычисленным. Заметим, что использование опции numeric не равнозначно указанию типа numeric у этих же переменных в формальных параметрах. В последнем случае проверка на тип производится сразу при обращении к процедуре и при несовпадении типов генерируется ошибка;

·         numeric. Это частный случай предыдущей опции, когда в списке [z1, z2, …] перечислены все ведущие переменные.

        Примеры 9. Обратите внимание на использование unapply с неопределенным количеством аргументов и с опцией numeric.

> 

restart:

{Shift+¿}

 

h := unapply(k+k^2, k);

{¿}

> 

[h[1], h[2], h[3]];

{¿}

> 

# unapply с неопределенным количеством аргументов

{Shift+¿}

 

k := 'k':  qw := unapply(

{Shift+¿}

 

   [nargs, sqrt(sum(args[k]^2, k = 1..nargs))/nargs]);

{¿}

> 

qw(1, 4, 3, 2, 1, 0, 7, 3, 2);

{¿}

> 

x := 'x': y := 'y': b = 'b': a := 11:

{Shift+¿}

 

h := unapply(x+y^2+a, [x, y]);

{Shift+¿}

 

[h(1, 2), h(a, b), h(b, a)];

{¿}

> 

# unapply с опцией numeric

{Shift+¿}

 

h := unapply(x+y^2+a, [x, y], numeric = x):

{Shift+¿}

 

[h(1, 2), h(a, b), h(b, a)];

{¿}

> 

h := unapply(x+y^2+a, [x::numeric, y]);

{¿}

> 

[h(1, 2), h(a ,b)];

{¿}

> 

h(b, a);

{¿}

Определение процедур по выражениям и переменным (2)

        Процедура makeproc расположена в пакете codegen.

        Команда makeproc(expr, [x::ty1, y::ty2, …], opts). Здесь: expr – произвольное выражение, x, y, … – переменные, ty1, ty2, … – необязательные типы переменных, opts – необязательная последовательность опций. Если переменная одна, то ее можно указывать без списка. По makeproc без опций создается процедура вида

 

proc(x::ty1, y::ty2, …) expr end proc;

 

то есть переменные x, y, … в данном случае играют роль формальных параметров. Возможные значения опций opts таковы:

·       parameters = [u::typ1, v::typ2, …] – установка формальных параметров процедуры и их типов. Правую часть опции обязательно представлять cписком;

·       locals = [lo1, lo2, …] – задание локальных переменных процедуры;

·       globals = [gl1, gl2, …] – задание глобальных переменных процедуры.
        По команде
makeproc с опциями parameters, locals и globals создается процедура вида

proc(u::typ1, v::typ2, …)

   local lo1, lo2, …;

   global gl1, gl2, … ;

   expr;

 end proc;

        Команда makeproc(L, [x::ty1, y::ty2, …], opts). Опции те же самые, что и в makeproc(expr, [x::ty1, y::ty2, …], opts), и действуют идентично. Синтаксис допускает две возможности:

1.    L – список уравнений a = expr1, b = expr2, …, где expr1, expr2, … – произвольные выражения, a, b, … – переменные. Любое уравнение в списке  [a = expr1, b = expr2, …] можно заменить выражением. По этой команде без опций создается процедура следующего вида

proc(x::ty1, y::ty2, …)

   local a, b, …;

   a := expr1; b := expr2; …;

end proc;

2.    L – имя массива выражений expr1, expr2, …(array([expr1, expr2, …])). Пусть m – количество элементов в L. Тогда по этой команде без опций создается процедура следующего вида

 

proc(x::ty1, y::ty2, …)

   local L;   

   L := array(1..т);        

   L[1] := expr1; L[2] := expr2; … ; L;

end proc;

По ней возвращается массив L.

        Примеры 10. Здесь демонстрируются варианты создания процедур командой makeproc.

> 

restart: with(codegen, makeproc):

{¿}

> 

g := x*y*sin(x+y)-y*sin(x-y): li := [x, y]:

{Shift+¿}

 

f := makeproc(g, li);

{¿}

> 

[f(1, 1), evalf(f(2, 1))];

{¿}

> 

restart:

{¿}

> 

tole := [s = abs(x-y), t = abs(x+y), g = a*s+b*t]:

{Shift+¿}

 

codegen[makeproc](tole, [x, y]);

{¿}

> 

tor := array([abs(x-y), abs(x+y), x]):

{Shift+¿}

 

mo := codegen[makeproc](tor, [x, y]);

{¿}

> 

eval(mo(a-b, a+b));

{¿}

Последовательности присваиваний

        Функция CompSeq(locals = li1, globals = li2, params = li3, lias). Здесь: lias = [name1 = expr1, name2 = expr2, …], где name1, name2, … – имена и expr1 , expr2, … – произвольные выражения, li1 – необязательный список имен локальных переменных, li2 – необязательный список имен глобальных переменных, li3 – необязательный список имен параметров. В lias любое уравнение можно заменить его правой частью, а в списке li3 у параметров можно указывать типы. Функция CompSeq является контейнером для представления совокупности последовательных присваиваний, которые задаются в виде уравнений элементами списка lias. Эту функцию можно подвергнуть упрощению (simplify) и оптимизации (optimize), а также преобразовать в процедуру (convert). В последнем случае аргументы locals = li1 и globals = li2 своими списками li1 и li2 формируют разделы локальных и глобальных переменных процедуры, а аргумент params = li3 списком li3 задает ее формальные параметры.

        Примеры 11. Здесь по последовательности присваиваний cs создается и выводится процедура gg. Затем по упрощенной последовательности simplify(cs) формируется и выводится процедура ggg. И, наконец, вычисляются значения gg(1, 2) и ggg(1, 2). Ясно, что gg(1, 2) = ggg(1, 2). Нетрудно видеть как, и за счет чего, упрощена процедура ggg по сравнению с эквивалентной ей процедурой gg.

> 

restart:

{¿}

> 

d := glo:

{Shift+¿}

 

cs := CompSeq(locals = [a, b, c],

{Shift+¿}

 

   globals = [d], params = [x, y],

{Shift+¿}

 

   [a = x+y, b = x-y+a, c = d+a+b, x+y+a+c]):

{¿}

> 

gg := convert(cs, procedure);

{¿}

 

gg := proc(x, y)

 

 

   local a, b, c;

 

 

   global glo;

 

 

   a := x+y; b := x-y+a; c := glo+a+b; x+y+a+c;

 

 

end proc;

 

> 

ggg := convert(simplify(cs), procedure);

{¿}

 

ggg := proc(x, y)

 

 

   global glo;

 

 

   5*x+3*y+glo;

 

 

end proc;

 

> 

gg(1, 2), ggg(1, 2);

{¿}

Задания:

  1. Даны действительные числа х, у. Определить, при­надлежит ли точка с координатами х, у заштрихованной части плоскости. Провести профилирование программы.

 

  1. Дана целочисленная квадратная матрица по­рядка 8. Найти наименьшее из значений элементов столбца, который обладает наибольшей суммой модулей элементов. Если таких столбцов несколько, то взять первый из них. Использовать define